package cn.jwtool.jwcache.buffer.data;

import cn.jwtool.jwcache.buffer.config.JwcatchConfig;
import cn.jwtool.jwcache.buffer.data.backup.RdbModel;
import cn.jwtool.jwcache.buffer.data.backup.Serialization;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author : willian fu
 * @version : 1.0
 * 数据持久化
 * 数据格式  数据头标识(CAFE BADB) 数据长度（4字节）数据内容
 * RDB方式恢复从持久化文件最末端开始读取最近一次备份
 * AOF方式恢复从持久化文件最头部开始按顺序读取每次的指令进行恢复
 */
public class BackUp {

    private static FileOutputStream rdbFile;
    private static FileOutputStream aofFile;

    //RDB固定头部
    private final static byte[] RDB_HEADER = {(byte) 0XCA, (byte) 0XFE, (byte) 0XBA, (byte) 0XDB};
    //AOF固定头部
    private final static byte[] AOF_HEADER = {(byte) 0XCA, (byte) 0XFE, (byte) 0XBA, (byte) 0XAF};

    /**
     * 加载备份文件
     *
     * @throws IOException
     */
    public void loadBackUpFile() throws IOException {
        File fileRdb = new File(getPath(true));
        File fileAof = new File(getPath(false));
        if (!fileRdb.exists() || fileRdb.isDirectory()) {
            fileRdb.createNewFile();
        }
        if (!fileAof.exists() && fileAof.isDirectory()) {
            fileAof.createNewFile();
        }
        rdbFile = new FileOutputStream(fileRdb, false);
        aofFile = new FileOutputStream(fileAof, true);
    }

    private static String getPath(boolean type) {
        return JwcatchConfig.getLogPath() + (type ? "\\RDB.rdb" : "\\AOF.aof");
    }

    /**
     * 获取字节码
     *
     * @param data 数据
     * @param type 持久化类型
     * @return 序列化后的字节码
     */
    private byte[] getBytes(Object data, boolean type) {
        byte[] serialization = Serialization.DEFAULT.serialization(data);
        //构造数据头部
        byte[] header = byteMerger(type ? RDB_HEADER : AOF_HEADER, intToBytes(serialization.length));
        return byteMerger(header, serialization);
    }

    /**
     * 整数字节数组表述，需要注意大小端模式
     *
     * @param data 整数
     * @return 字节数组形式
     */
    private byte[] intToBytes(int data) {
        return new byte[]{
                (byte) (data >> 24 & 0xff),
                (byte) (data >> 16 & 0xff),
                (byte) (data >> 8 & 0xff),
                (byte) (data & 0xff)};
    }

    /**
     * 合并2个字节数组
     *
     * @param bt1
     * @param bt2
     * @return
     */
    private byte[] byteMerger(byte[] bt1, byte[] bt2) {
        byte[] bt3 = new byte[bt1.length + bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }


    /**
     * 追加日志到持久化文件
     *
     * @param data 数据内容
     */
    public void writeToFile(Object data, boolean type) throws IOException {
        if (type) {
            rdbFile.write(getBytes(data, type));
            rdbFile.flush();
        } else {
            aofFile.write(getBytes(data, type));
            aofFile.flush();
        }
    }

    /**
     * 执行数据持久化
     * @throws IOException
     */
    public void runRdbTask() throws IOException {
        this.writeToFile(new RdbModel(Catch.data, Catch.expired), true);
    }

    private static boolean has = false;

    private BackUp(){
        synchronized (BackUp.class){
            //防止通过反射实例化
            if(!has){
                has = true;
            }else{
                throw new RuntimeException("强制单例，不允许再次实例化");
            }
        }
    }

    public synchronized static BackUp getInstance() {
        return Builder.INSTANCE;
    }

    private static final class Builder {
        private static final BackUp INSTANCE = new BackUp();
    }
}
